package com.yfbao.horizon.docker.service;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.yfbao.horizon.docker.dao.DubboRollBackDao;
import com.yfbao.horizon.docker.dao.TestTableDao;
import com.yfbao.horizon.docker.entity.DubboRollBack;
import com.yfbao.horizon.docker.entity.TestTable;
import lombok.extern.slf4j.Slf4j;
import org.nustaq.serialization.FSTConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class DubboRollBackService {

    @Autowired
    private DubboRollBackDao dubboRollBackDao;

    @Autowired
    private TestTableDao testTableDao;

    @Autowired
    private static ApplicationContext context;

    private static Snowflake snowflake = IdUtil.getSnowflake();

    //防止重复提交
    private static Cache<String, Object> runningCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).maximumSize(5000).build();

    //防止重复处理
    private static Cache<String, Object> finishCache = Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).maximumSize(5000).build();

    private static  FSTConfiguration FST = FSTConfiguration.createDefaultConfiguration();
    private static String EMPTY_STR = "";
//            .createStructConfiguration();

    @Transactional(rollbackFor=Exception.class)
    public void mockCreatData(){
        TestTable testTable = new TestTable();
        testTable.setId(snowflake.nextId());
        log.info("testTable id:"+testTable.getId());
        testTable.setCreateTime(new Date());
        testTable.setSerialNumber(snowflake.nextId());
        testTable.setData("test-creat");
        testTableDao.insert(testTable);
        log.info("SerialNumber:"+testTable.getSerialNumber());
        afterRollBackEvent("create",this.getClass().getName(),"rollBackCreat",testTable.getSerialNumber(),testTable);
    }

    @Transactional(rollbackFor=Exception.class)
    public void mockUpdateData(Long id){
        TestTable testTable = new TestTable();
        testTable.setData("test-creat-2");
        testTable.setId(id);
        testTable.setSerialNumber(snowflake.nextId());
        System.out.println("udpate SerialNumber:"+testTable.getSerialNumber());

//        QueryWrapper<TestTable> queryWrapper = new QueryWrapper<>();
//        queryWrapper.lambda().eq(TestTable::getData,"test-creat");
        TestTable testTables = testTableDao.selectById(id);

        testTableDao.updateById(testTable);
        log.info("SerialNumber:"+testTable.getSerialNumber());
        afterRollBackEvent("update",this.getClass().getName(),"rollBackUpdate",testTable.getSerialNumber(),testTables);
    }

    @Transactional(rollbackFor=Exception.class)
    public boolean rollBackUpdate(DubboRollBack dubboRollBack){
        String rollBackData = dubboRollBack.getRollBackData();
        TestTable testT= (TestTable) FST.asObject(rollBackData.getBytes());
        log.info("test des data:"+testT.getData());
        TestTable testTable = new TestTable();
        testTable.setId(testT.getId());
        testTable.setData(testT.getData());
        testTable.setSerialNumber(dubboRollBack.getSerialNumber());
        testTableDao.updateById(testTable);
        log.info("rollback updateData , SerialNumber:"+dubboRollBack.getSerialNumber());
        return true;
    }

    public boolean rollBackCreat(DubboRollBack dubboRollBack){
        String rollBackData = dubboRollBack.getRollBackData();
        if(StringUtils.isNotBlank(rollBackData)){
            TestTable testTable = (TestTable) FST.asObject(rollBackData.getBytes());
            log.info("test des data:"+testTable.getData());
        }
        log.info("rollback createDate , SerialNumber:"+dubboRollBack.getSerialNumber());
        return true;
    }


    public void rollBackDubbo(Long serialNumber){
        log.info("begin do rollback:"+serialNumber);
        QueryWrapper<DubboRollBack> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(DubboRollBack::getSerialNumber,serialNumber);
        List<DubboRollBack> dubboRollBacks = dubboRollBackDao.selectList(queryWrapper);
        if(dubboRollBacks.isEmpty()){
            return;
        }
        for (DubboRollBack dubboRollBack : dubboRollBacks) {
            if(dubboRollBack.getRetryTime()>3){
                log.warn("serialNumber:"+serialNumber+", rollback task retry over 3, break...");
                break;
            }
            boolean b = methodInvoke(dubboRollBack);
            if(b){
                dubboRollBack.setRollbackStatus(2);
            }else {
                dubboRollBack.setRetryTime(dubboRollBack.getRetryTime()+1);
                dubboRollBack.setRollbackStatus(1);
            }
            dubboRollBackDao.updateById(dubboRollBack);
        }
    }


    private boolean methodInvoke(DubboRollBack dubboRollBack){
        String className = dubboRollBack.getClassName();
        String methodName = dubboRollBack.getMethod();
        Object bean = context.getBean(className);
        if(bean ==null){
            log.error("can't find bean:"+className);
            return false;
        }
        try {
            Method method = bean.getClass().getMethod(methodName);
            Object invoke = method.invoke(bean, dubboRollBack);
            return (Boolean)invoke;
        }catch (Throwable t){
            log.error("method invoke error, method:"+methodName,t);
        }
        return false;
    }


    private void afterRollBackEvent(String rollBackType, String className,String methodName,Long serialNumber,Object data){
        DubboRollBack dubboRollBack = new DubboRollBack();
        dubboRollBack.setId(snowflake.nextId());
        dubboRollBack.setClassName(className);
        dubboRollBack.setCreateTime(new Date());
        dubboRollBack.setMethod(methodName);
        if(data!=null){
            String s = parseToString(data);
            if(s ==null){
                throw new RuntimeException("数据转换异常");
            }
            dubboRollBack.setRollBackData(s);
        }else {
            dubboRollBack.setRollBackData(EMPTY_STR);
        }
        dubboRollBack.setRollBackType(rollBackType);
        dubboRollBack.setSerialNumber(serialNumber);
        dubboRollBackDao.insert(dubboRollBack);
    }


    private String parseToString(Object obj){
        try {
            String s = new String(FST.asByteArray(obj), "utf-8");
            return s;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

}
